/***************************************************************************
 *  Title: Runtime environment 
 * -------------------------------------------------------------------------
 *    Purpose: Runs commands
 *    Author: Stefan Birrer
 *    Version: $Revision: 1.1 $
 *    Last Modification: $Date: 2005/10/13 05:24:59 $
 *    File: $RCSfile: runtime.c,v $
 *    Copyright: (C) 2002 by Stefan Birrer
 ***************************************************************************/
/***************************************************************************
 *  ChangeLog:
 * -------------------------------------------------------------------------
 *    $Log: runtime.c,v $
 *    Revision 1.1  2005/10/13 05:24:59  sbirrer
 *    - added the skeleton files
 *
 *    Revision 1.6  2002/10/24 21:32:47  sempi
 *    final release
 *
 *    Revision 1.5  2002/10/23 21:54:27  sempi
 *    beta release
 *
 *    Revision 1.4  2002/10/21 04:49:35  sempi
 *    minor correction
 *
 *    Revision 1.3  2002/10/21 04:47:05  sempi
 *    Milestone 2 beta
 *
 *    Revision 1.2  2002/10/15 20:37:26  sempi
 *    Comments updated
 *
 *    Revision 1.1  2002/10/15 20:20:56  sempi
 *    Milestone 1
 *
 ***************************************************************************/
#define __RUNTIME_IMPL__

/************System include***********************************************/

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

/************Private include**********************************************/
#include "runtime.h"
#include "io.h"
#include "alias.h"
#include "interpreter.h"

/************Defines and Typedefs*****************************************/
/*  #defines and typedefs should have their names in all caps.
 *  Global variables begin with g. Global constants with k. Local
 *  variables should be in all lower case. When initializing
 *  structures and arrays, line everything up in neat columns.
 */

#define NBUILTINCOMMANDS (sizeof BuiltInCommands / sizeof(char*)) // number of builtincommands

typedef struct bgjob_l {
  int jobNumber;
  pid_t pid;
  char* commandLine; // store the name of the job as described by the first argument (e.g. ./hello)
  int state; // 0 = stopped, 1 = running
  struct bgjob_l* next;
} bgjobL; // use a linked list to store the process id

typedef struct fgjob_l {
  pid_t pgid; // process grounp id; if pgid = -1, no foreground job; the same as its pid
  char* commandLine; // store the name of the job as described by the first argument (e.g. ./hello)
} fgjobL;

/************Global Variables*********************************************/

bgjobL* bgjobs = NULL; // linked list of background job
fgjobL fgjob = {-1, ""}; // need to store the foreground job to send signal to it/ to store it in bgjob list when stopped
char outputBuffer[BUFFERSIZE];

/************Function Prototypes******************************************/

/* run command */
void RunCmd(commandT** cmd, int n);
void RunCmdFork(commandT*, bool); // static functions are not visible to other files
/* runs an external program command after some checks */
static void RunExternalCmd(commandT*, bool);
/* resolves the path and checks for exutable flag */
static bool ResolveExternalCmd(commandT*);
/* forks and runs a external program */
static void Exec(commandT*, bool);
/* runs a builtin command */
static void RunBuiltInCmd(commandT*);
/* checks whether a command is a builtin command */
bool IsBuiltIn(char*);
// to append a background job to a job list
int appendBgJobNode(pid_t, char*, int);
// delete a bg job from the linked list
void deleteBgJobNode(pid_t, bool); //  called in tsh.c
// For the foreground processes, block SIGCHILD, waitpid until the child terminates, unblock
static void waitpid_fg(pid_t childPid);
// a helper function of the write ssize_t write(int fd, const void *buf, size_t count); and check its possible errror
void writeWrapper(const void* buf, size_t nbyte);
// malloc and create a commandT structure
commandT* CreateCmdT(int n);
// free the commandT structure
void ReleaseCmdT(commandT **cmd);
// create a background jobs node
bgjobL* createBgJobNode(pid_t pid, char* commandLine, int state);
// append the bg job node in the linked list
int appendBgJobNode(pid_t pid, char* commandLine, int state);
// print all background jobs
static void printBgJobs();

/************External Declaration*****************************************/

/**************Implementation***********************************************/
int total_task;

void RunCmd(commandT** cmd, int n) { // n is the number of task
  int i;
  total_task = n;

  if (n == 1) // only one command
    RunCmdFork(cmd[0], TRUE); // true defined in config.h
  else { // piped commands
    RunCmdPipe(cmd[0], cmd[1]);
    for (i = 0; i < n; i++)
      ReleaseCmdT(&cmd[i]);
  }
}

void RunCmdFork(commandT* cmd, bool forkcmd) // bool defined in config.h, cmd is passed as pointer because not want to copy?
{
  if (cmd->argc <= 0)
    return;

  char* cmdl = NULL;
  char* cmdArg = NULL;

  if (IsBuiltIn(cmd->argv[0]))
     RunBuiltInCmd(cmd); 
  else if((cmdl = findAliasCmd(cmd->argv[0])) != NULL)
  {  
     if(cmd->argc == 1)
     {
	//ReleaseCmdT(cmd);
	Interpret(cmdl);
	free(cmdl);
     } else if((cmd->argc == 2) && ((cmdArg = findAliasCmd(strchr(cmd->cmdline, ' ')+ 1)) != NULL))
     {
	free(cmd->argv[0]);
	cmd->argv[0] = cmdl;
	free(cmd->argv[1]);
	cmd->argv[1] = cmdArg;
	RunCmdFork(cmd, TRUE);
     }
  }
  else
    RunExternalCmd(cmd, forkcmd);
}

void RunCmdBg(commandT* cmd) { // not sure if it is useful
}

void RunCmdPipe(commandT* cmd1, commandT* cmd2) {
}

void RunCmdRedirOut(commandT* cmd, char* file) {
}

void RunCmdRedirIn(commandT* cmd, char* file) {
}

void RunExternalCmd(commandT* cmd, bool fork)
{
  if (ResolveExternalCmd(cmd))
    Exec(cmd, fork);
  else {
    printf("%s: command not found\n", cmd->argv[0]);
    fflush(stdout); 
    /* If the given stream was open for writing 
       (or if it was open for updating and the last i/o operation was an output operation) 
       any unwritten data in its output buffer is written to the file.*/
    ReleaseCmdT(&cmd);
  }
}

/*Find the executable based on search list provided by environment variable PATH*/
// retrun true and save the command name in cmd->name
static bool ResolveExternalCmd(commandT* cmd)
{
  char *pathlist, *c; // string
  char buf[1024];
  int i, j;
  struct stat fs;

  if (strchr(cmd->argv[0], '/') != NULL) { 
    // locate the first occurence of char in string and return a pointer pointing to it

    if (stat(cmd->argv[0], &fs) >= 0) {
      // int stat(const char *restrict path, struct stat *restrict buf);
      // Upon successful completion, 0 shall be returned. Otherwise, -1.
      // get information about the file from the path. If succeeded, put the info in fs
      if (S_ISDIR(fs.st_mode) == 0) 
     	// a macro to test for directory. return nonzero if true, and o otherwise..
	if (access(cmd->argv[0], X_OK) == 0) { 
	  //*Whether it's an executable or the user has required permisson to run it*/
	  cmd->name = strdup(cmd->argv[0]);
	  return TRUE;
        }
    }
    return FALSE;
  }
  // $PATH variable is specified as a list of one or more directory names separated by colon (:) characters
  // PATH specifies a set of directories where executable programs are located
  pathlist = getenv("PATH"); // get environment variable return the pointer
  if (pathlist == NULL)
    return FALSE;

  i = 0;
  while (i < strlen(pathlist)) {
    c = strchr(&(pathlist[i]), ':'); // c is a pointer pointing to :
    if (c != NULL) { // get the addresses
      for (j = 0; c != &(pathlist[i]); i++, j++)
        buf[j] = pathlist[i];
      i++;
    }
    else { // no :, there is only one address
      for (j = 0; i < strlen(pathlist); i++, j++)
        buf[j] = pathlist[i];
    }
    buf[j] = '\0';

    strcat(buf, "/"); // concatenation
    strcat(buf, cmd->argv[0]);

    if (stat(buf, &fs) >= 0) {
      if (S_ISDIR(fs.st_mode) == 0)
        if (access(buf, X_OK) == 0) { //Whether it's an executable or the user has required permisson to run it
          cmd->name = strdup(buf); 
          return TRUE;
        }
    }
  }
  return FALSE; /*The command is not found or the user don't have enough priority to run.*/
}

static void Exec(commandT* cmd, bool forceFork) {
  pid_t pid;

  if ((pid = fork()) == 0) { // create a child process
    setpgid(0, 0); // all the fg and bg jobs should do this, otherwise error
    execv(cmd->argv[0], cmd->argv); // exe the cmd
    exit(0);
  }
  else {// things to be done by parent
    if (!cmd->bg) {// if it is in the foreground wait until it is finish and then reap the zombie
      fgjob.pgid = pid;
      fgjob.commandLine = cmd->cmdline; // there may be signal like ctrl-z to stop the fg job, and we need to add it to bg job
      
      waitpid_fg(pid); // wait until it terminates

      fgjob.pgid = -1; // negative one means no foreground job
    }
    else // background jobs
      appendBgJobNode(pid, cmd->cmdline, 1);
    // run at background means not waiting until it is finished
    // the things to do are:
    // 1. put the job to the bg job linked list
    // 2. when the job terminates, reap and
    // 3. put something like [2]   Done            ./hello to the output stream without printing out immediately
  }
}

bool IsBuiltIn(char* cmd) // cmd is the first argument of a command line
{
  if (!strcmp(cmd, "echo") || !strcmp(cmd, "sleep") || !strcmp(cmd, "SLEEP")
      || !strcmp(cmd, "jobs") || !strcmp(cmd, "fg") || !strcmp(cmd, "bg") || !strcmp(cmd, "bash")
      || !strncmp(cmd, "ls", 2) || !strcmp(cmd, "pwd") || !strcmp(cmd, "cat") || !strcmp(cmd, "which")
      || !strcmp(cmd, "grep") || !strcmp(cmd, "whoami") || !strcmp(cmd, "whatis") 
      || !strcmp(cmd, "wc") || !strcmp(cmd, "cd") || !strcmp(cmd, "mkdir") || !strcmp(cmd, "alias")
      || !strcmp(cmd, "unalias"))
    // Note Test cd command here first, although cd is not built in.
    return TRUE;
  return FALSE; 
  /* does not work. Cannot find some commands such as sleep, jobs, cd, fg, bg
     struct stat fs;
     char* cmdPath = calloc(strlen("/bin/") + strlen(cmd) + 1);
     strcpy(cmdPath, "/bin/");
     strcat(cmdPath, cmd);
     return stat(cmd, &fs) >= 0
  */
}

// given a job number in the command, search for its pid
pid_t convertJobNumber2Pid(commandT* cmd) { 
  pid_t jobPid = -1;
  bgjobL* p = NULL;

  if (cmd->argv[1] != NULL) { // a job number is given
    int jobNumber = atoi(cmd->argv[1]);
    for (p = bgjobs; p != NULL && p->jobNumber != jobNumber; p = p->next);
    if (p != NULL) // p->jobNumber == jobNumber (found)
      jobPid = p->pid;
  }
  else { // a job number is not given
    if (bgjobs != NULL) {
      for (p = bgjobs; p->next != NULL; p = p->next); // search for the last job
      jobPid = p->pid;
    }    
  }

  return jobPid; // if the job cannot be found, return -1
}

static void waitpid_fg(pid_t childPid) {
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGCHLD); // add SIGCHLD to set
  sigprocmask(SIG_BLOCK, &mask, NULL); // block the set of signal with SIGCHLD only
  waitpid(childPid, NULL, WUNTRACED); // parent waits until child execution completes
  sigprocmask(SIG_UNBLOCK, &mask, NULL); // unblock SIGCHLD
  return;
}

void writeWrapper(const void* buf, size_t nbyte) {
  if(write(STDOUT_FILENO, buf, nbyte) < 0)
    perror("write command error");

  return;
}
///////BELOW///////////////HELPER FUNCTIONS USED IN RunBuiltInCmd///////////////////////////

void execEcho(commandT* cmd) {
  char* p = strchr(cmd->cmdline, 'o');
  p += 2;
  if (*p == '\"') {
    p++;
    char* i;
    for (i = p ; *i != '\"'; i++);
    *i = '\0';
  }
  writeWrapper(p, strlen(p));
  writeWrapper("\n", strlen("\n"));

  return;
}

void execSleep(commandT* cmd) {
  pid_t pid;

  if ((pid = fork()) == 0) {
    setpgid(0, 0); // set the process group id of the child to be its pid
    sleep(atoi(cmd->argv[1]));
    exit(0);
  }
  else { // theoretically should allow user to stop sleep and put it to bg linked list
    fgjob.pgid = pid;
    fgjob.commandLine = cmd->cmdline;

    waitpid_fg(pid);

    fgjob.pgid = -1; // no foreground jobs
  }
}

void exeAlias(commandT* cmd)
{
   if(cmd->argc == 1)
      printAlias();
   else if(cmd->argc == 2) 
      appendAliasLL(cmd);
   else 
      assert(TRUE);
}

void execFg(commandT* cmd) {
  // parent wait until child execution completes and remove from background job list
  pid_t fgJobPid = convertJobNumber2Pid(cmd);

  if (fgJobPid == -1) // the job cannot be found or there is no bg jobs
    return;
  else {
    bgjobL* p = NULL;
    for (p = bgjobs; p->pid != fgJobPid; p = p->next); // must be able to find
    if (p->state == 0) {// the job is stopped
      kill(fgJobPid, SIGCONT); // send signal bgJobPid to restart it
      p->state = 1; // running again
    }
    //printf("%s = fg job\n", p->commandLine);
    fgjob.pgid = fgJobPid;
    fgjob.commandLine = strdup(p->commandLine);
    deleteBgJobNode(fgJobPid, FALSE); // remove from background job list
    waitpid_fg(fgJobPid);
    //printf("fg Done\n");
    fgjob.pgid = -1;
    free(fgjob.commandLine);
  }
}

void execBg(commandT* cmd) {
  pid_t bgJobPid = convertJobNumber2Pid(cmd);

  if (bgJobPid == -1) // cannot find that jobs in the background list
    return; // do nothing
  else {
    bgjobL* p = NULL;
    for (p = bgjobs; p->pid != bgJobPid; p = p->next);
    if (p->state == 0) {// the job is stopped
      //kill(bgJobPid, SIGCONT); // send signal bgJobPid to restart it
      //printf("%s is running as a background job\n", p->commandLine);
      kill(-p->pid, SIGCONT);
      p->state = 1; // running again
    }
  }
}

void execStandardFgjob(commandT* cmd, char* exeFileName) {
  pid_t pid;

  if ((pid = fork()) == 0) {
    setpgid(0, 0); // set the process group id of the child to be its pid
    execv(exeFileName, cmd->argv);
    exit(0);
  }
  else {
    fgjob.pgid = pid;
    fgjob.commandLine = cmd->cmdline;

    waitpid_fg(pid);

    fgjob.pgid = -1; // no foreground jobs
  }
}

void execStandardBgjob(commandT* cmd, char* exeFileName) {
  pid_t pid;

  if ((pid = fork()) == 0) {
    setpgid(0, 0);
    execv(exeFileName, cmd->argv);
    exit(0);
  }
  else
    appendBgJobNode(pid, cmd->cmdline, 1);
}

///////ABOVE///////////////HELPER FUNCTIONS USED IN RunBuiltInCmd///////////////////////////

void RunBuiltInCmd(commandT* cmd) {

  if (!strcmp(cmd->argv[0], "echo")) // fg + bg if the it is an echo cmd
    execEcho(cmd);
  else if (!strcmp(cmd->argv[0], "SLEEP") || !strcmp(cmd->argv[0], "sleep")) // fg
    execSleep(cmd);
  else if (!strcmp(cmd->argv[0], "jobs")) // fg
    printBgJobs();
  else if (!strcmp(cmd->argv[0], "fg")) // fg a backgrounded job
    execFg(cmd);
  else if (!strcmp(cmd->argv[0], "bg")) // send signal to a job in background list, if it is stopped, then change its state
    execBg(cmd);
  else if (!strncmp(cmd->argv[0], "ls",2)) // fg
  {
     if((cmd->argv[1] != NULL) && (!strcmp(cmd->argv[1], "~/")))
     {
	free(cmd->argv[1]);
	char* loginName = getenv("USER");
	cmd->argv[1] = malloc(strlen("/home/") + strlen(loginName) + 1);
	strcpy(cmd->argv[1], "/home/");
	strcat(cmd->argv[1], loginName);	
     }
     execStandardFgjob(cmd, "/bin/ls");
  } else if (!strcmp(cmd->argv[0], "bash")) // bash is always run in background
    if(cmd->bg)
      execStandardBgjob(cmd, "/bin/bash");
    else 
      execStandardFgjob(cmd, "/bin/bash");
  else if (!strcmp(cmd->argv[0], "pwd")) // fg 
    execStandardFgjob(cmd, "/bin/pwd");
  else if (!strcmp(cmd->argv[0], "cat")) // fg cat command
    execStandardFgjob(cmd, "/bin/cat");
  else if (!strcmp(cmd->argv[0], "which")) // fg which command
    execStandardFgjob(cmd, "/bin/which");
  else if (!strcmp(cmd->argv[0], "whoami")) // fg whoami command
    execStandardFgjob(cmd, "/usr/bin/whoami");
  else if (!strcmp(cmd->argv[0], "whatis")) // fg whatis command
    execStandardFgjob(cmd, "/usr/bin/whatis");
  else if (!strcmp(cmd->argv[0], "wc")) // fg wc command
    execStandardFgjob(cmd, "/usr/bin/wc");
  else if (!strcmp(cmd->argv[0], "cd")) { // fg cd command
    if(cmd->argv[1] == NULL) {
      char* loginName = getenv("USER"); // get the login name from environment
      if(loginName == NULL)
	perror("getenv(\"USER\")");

      char* homeDir = malloc(strlen("/home/") + strlen(loginName) + 1);
      strcpy(homeDir, "/home/");
      strcat(homeDir, loginName);       
      if(chdir(homeDir) < 0)
	perror("chdir with arguments");

      free(homeDir);
    } else {
      if(chdir(cmd->argv[1]) < 0)
	perror("chdir with no arguments");
    }
  }
  else if (!strcmp(cmd->argv[0], "mkdir")) // fg mkdir command
     execStandardFgjob(cmd, "/usr/bin/mkdir");
  else if (!strcmp(cmd->argv[0], "grep"))
     execStandardFgjob(cmd, "/bin/grep");
  else if (!strcmp(cmd->argv[0], "alias"))
     exeAlias(cmd);
  else if (!strcmp(cmd->argv[0], "unalias"))
     unalias(cmd); // alias.c
}

void CheckJobs() {
}


commandT* CreateCmdT(int n) { // n is the number of argument
  int i;
  commandT * cd = malloc(sizeof(commandT) + sizeof(char *) * (n + 1));
  cd -> name = NULL;
  cd -> cmdline = NULL;
  cd -> is_redirect_in = cd -> is_redirect_out = 0;
  cd -> redirect_in = cd -> redirect_out = NULL;
  cd -> argc = n;
  for(i = 0; i <= n; i++)
    cd -> argv[i] = NULL; // mark the end of argument
  return cd;
}

/*Release and collect the space of a commandT struct*/
void ReleaseCmdT(commandT **cmd) {
  int i;
  if ((*cmd)->name != NULL) free((*cmd)->name);
  if ((*cmd)->cmdline != NULL) free((*cmd)->cmdline);
  if ((*cmd)->redirect_in != NULL) free((*cmd)->redirect_in);
  if ((*cmd)->redirect_out != NULL) free((*cmd)->redirect_out);
  for (i = 0; i < (*cmd)->argc; i++)
    if ((*cmd)->argv[i] != NULL) free((*cmd)->argv[i]);
  free(*cmd);
}

///////BELOW///////////////HELPER FUNCTIONS FOR LINKED LIST OPERATION OF BGJOB_L///////////////////////////

bgjobL* createBgJobNode(pid_t pid, char* commandLine, int state) {
  // if the args are ./myspin 3 &, commandLine should be ./myspin 3
  bgjobL* newBgJob = (bgjobL*)malloc(sizeof(bgjobL));
  bgjobL* p = NULL;
  if (bgjobs == NULL)
    newBgJob->jobNumber = 1;
  else {
    // find the last bgjob and increase jobNumber
    for (p = bgjobs; p->next != NULL;  p = p->next); 
    newBgJob->jobNumber = p->jobNumber + 1;
  }
  newBgJob->pid = pid;
  newBgJob->commandLine = strdup(commandLine); // new memory is allocated, remember to free!!
  newBgJob->state = state; // it should be running when created
  newBgJob->next = NULL;
  
  return newBgJob;
}

int appendBgJobNode(pid_t pid, char* commandLine, int state) {
  bgjobL* p = NULL; // current node
  bgjobL* newBgJob = createBgJobNode(pid, commandLine, state);

  if (bgjobs == NULL) // if the list of bg jobs is empty
    bgjobs = newBgJob;
  else {
    for (p = bgjobs; p->next != NULL;  p = p->next); // want to find the last node of the list
    p->next = newBgJob;
  }

  return newBgJob->jobNumber;
}

// when the job terminate, reap the zombie, put output to buffer
void deleteBgJobNode(pid_t pid, bool print) { // use the pid to find the job number and job name and delete it from memory
  bgjobL* prev = NULL;
  bgjobL* current = bgjobs;

  // find the element to be deleted
  while (current != NULL && current->pid != pid) {
    prev = current;
    current = current->next;
  }

  if ((current != NULL) && print) { // some cases require deleting only
    char newMessage[128];
    sprintf(newMessage, "[%d]   Done                    %s\n", current->jobNumber, current->commandLine);
    strcat(outputBuffer, newMessage);
  }

  if (current != NULL) { // found
    if (current == bgjobs)
      bgjobs = bgjobs->next;
    else
      prev->next = current->next;
    free(current->commandLine); // it is dynamically created
    free(current);
  }
}

// print all background jobs
static void printBgJobs(void) {
  bgjobL* p = NULL;
  char newMessage[128];

  for (p = bgjobs; p != NULL; p = p->next) {
    if (p->state == 0)
      sprintf(newMessage, "[%d]   Stopped                 %s\n", p->jobNumber, p->commandLine);
    else if (p->state == 1)
      sprintf(newMessage, "[%d]   Running                 %s &\n", p->jobNumber, p->commandLine);
    writeWrapper(newMessage, strlen(newMessage));
  }
}
///////ABOVE///////////////HELPER FUNCTIONS FOR LINKED LIST OPERATION OF BGJOB_L///////////////////////////
